أطلق العنان لحزمة 'email' في بايثون. تعلم بناء رسائل MIME المعقدة وتحليل البريد الوارد لاستخلاص البيانات بفعالية وعالميًا.
إتقان حزمة البريد الإلكتروني في بايثون: فن بناء رسائل MIME والتحليل القوي
يظل البريد الإلكتروني حجر الزاوية في التواصل العالمي، ولا غنى عنه في المراسلات الشخصية والعمليات التجارية وإشعارات الأنظمة الآلية. وراء كل بريد إلكتروني غني بالنصوص، وكل مرفق، وكل توقيع منسق بعناية، تكمن تعقيدات ملحقات بريد الإنترنت متعددة الأغراض (MIME). بالنسبة للمطورين، وخاصة أولئك الذين يعملون بلغة بايثون، يعد إتقان كيفية بناء وتحليل رسائل MIME هذه برمجيًا مهارة حيوية.
توفر حزمة email
المدمجة في بايثون إطار عمل قويًا وشاملاً للتعامل مع رسائل البريد الإلكتروني. فهي ليست مخصصة فقط لإرسال نصوص بسيطة؛ بل صُممت لتجريد التفاصيل المعقدة لبروتوكول MIME، مما يتيح لك إنشاء رسائل بريد إلكتروني متطورة واستخراج بيانات محددة من الرسائل الواردة بدقة ملحوظة. سيأخذك هذا الدليل في رحلة عميقة لاستكشاف الجانبين الأساسيين لهذه الحزمة: بناء رسائل MIME لإرسالها وتحليلها لاستخراج البيانات، مع تقديم منظور عالمي لأفضل الممارسات.
إن فهم كل من البناء والتحليل أمر بالغ الأهمية. عندما تقوم ببناء رسالة، فأنت تحدد بشكل أساسي هيكلها ومحتواها لنظام آخر ليفسره. وعندما تقوم بالتحليل، فأنت تفسر هيكلًا ومحتوى حدده نظام آخر. إن الفهم العميق لأحدهما يساعد بشكل كبير في إتقان الآخر، مما يؤدي إلى تطبيقات بريد إلكتروني أكثر مرونة وقابلية للتشغيل البيني.
فهم MIME: العمود الفقري للبريد الإلكتروني الحديث
قبل الخوض في تفاصيل بايثون، من الضروري فهم ماهية MIME وسبب أهميته البالغة. في الأصل، كانت رسائل البريد الإلكتروني تقتصر على النصوص العادية (أحرف ASCII ذات 7 بت). وقد وسّع MIME، الذي تم تقديمه في أوائل التسعينيات، إمكانيات البريد الإلكتروني لدعم:
- الأحرف غير ASCII: السماح بالنصوص بلغات مثل العربية، الصينية، الروسية، أو أي لغة أخرى تستخدم أحرفًا خارج مجموعة ASCII.
- المرفقات: إرسال ملفات مثل المستندات، الصور، الصوت، والفيديو.
- تنسيق النصوص الغنية: رسائل البريد الإلكتروني بتنسيق HTML مع التغميق، والمائل، والألوان، والتخطيطات.
- أجزاء متعددة: دمج النصوص العادية، و HTML، والمرفقات ضمن بريد إلكتروني واحد.
يحقق MIME ذلك عن طريق إضافة ترويسات محددة إلى رسالة البريد الإلكتروني وهيكلة محتواها إلى "أجزاء" مختلفة. تشمل ترويسات MIME الرئيسية التي ستواجهها:
Content-Type:
تحدد نوع البيانات في جزء ما (على سبيل المثال،text/plain
،text/html
،image/jpeg
،application/pdf
،multipart/alternative
). وغالبًا ما تتضمن أيضًا معلمةcharset
(مثلcharset=utf-8
).Content-Transfer-Encoding:
تشير إلى كيفية فك تشفير المحتوى من قبل عميل البريد الإلكتروني (على سبيل المثال،base64
للبيانات الثنائية،quoted-printable
للنصوص التي تحتوي على بعض الأحرف غير ASCII).Content-Disposition:
تقترح كيف يجب على عميل البريد الإلكتروني للمستلم عرض الجزء (على سبيل المثال،inline
للعرض داخل نص الرسالة،attachment
لملف ليتم حفظه).
حزمة email
في بايثون: نظرة عميقة
حزمة email
في بايثون هي مكتبة شاملة مصممة لإنشاء وتحليل وتعديل رسائل البريد الإلكتروني برمجيًا. وهي مبنية حول مفهوم كائنات Message
، التي تمثل بنية البريد الإلكتروني.
تشمل الوحدات الرئيسية داخل الحزمة:
email.message:
يحتوي على الصنف الأساسيEmailMessage
، وهو الواجهة الرئيسية لإنشاء رسائل البريد الإلكتروني والتعامل معها. إنه صنف مرن للغاية يتعامل مع تفاصيل MIME تلقائيًا.email.mime:
يوفر أصنافًا قديمة (مثلMIMEText
،MIMEMultipart
) التي توفر تحكمًا أكثر وضوحًا في بنية MIME. بينما يُفضل عمومًا استخدامEmailMessage
في الكود الجديد نظرًا لبساطته، يمكن أن يكون فهم هذه الأصناف مفيدًا.email.parser:
يقدم أصنافًا مثلBytesParser
وParser
لتحويل بيانات البريد الإلكتروني الأولية (بايتات أو سلاسل نصية) إلى كائناتEmailMessage
.email.policy:
تحدد السياسات التي تتحكم في كيفية بناء رسائل البريد الإلكتروني وتحليلها، مما يؤثر على ترميز الترويسات، ونهايات الأسطر، والتعامل مع الأخطاء.
بالنسبة لمعظم حالات الاستخدام الحديثة، ستتفاعل بشكل أساسي مع الصنف email.message.EmailMessage
لكل من بناء الرسائل وككائن رسالة محللة. تعمل أساليبه على تبسيط ما كان في السابق عملية أكثر تفصيلاً مع أصناف email.mime
القديمة.
بناء رسائل MIME: إنشاء رسائل البريد الإلكتروني بدقة
يتضمن بناء رسائل البريد الإلكتروني تجميع مكونات مختلفة (نص، HTML، مرفقات) في بنية MIME صالحة. يعمل الصنف EmailMessage
على تبسيط هذه العملية بشكل كبير.
رسائل البريد الإلكتروني النصية الأساسية
أبسط بريد إلكتروني هو النص العادي. يمكنك إنشاء واحد وتعيين الترويسات الأساسية بسهولة:
from email.message import EmailMessage
msg = EmailMessage()
msg['Subject'] = 'Greetings from Python'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
msg.set_content('Hello, this is a plain text email sent from Python.\n\nBest regards,\nYour Python Script')
print(msg.as_string())
الشرح:
EmailMessage()
ينشئ كائن رسالة فارغ.- الوصول الشبيه بالقاموس (
msg['Subject'] = ...
) يقوم بتعيين الترويسات الشائعة. set_content()
يضيف المحتوى الأساسي للبريد الإلكتروني. بشكل افتراضي، يستنتجContent-Type: text/plain; charset="utf-8"
.as_string()
يقوم بتحويل الرسالة إلى تنسيق سلسلة نصية مناسبة للإرسال عبر SMTP أو الحفظ في ملف.
إضافة محتوى HTML
لإرسال بريد إلكتروني بتنسيق HTML، ما عليك سوى تحديد نوع المحتوى عند استدعاء set_content()
. من الممارسات الجيدة توفير بديل نصي عادي للمستلمين الذين لا تعرض عملاء بريدهم الإلكتروني HTML، أو لأسباب تتعلق بإمكانية الوصول.
from email.message import EmailMessage
msg = EmailMessage()
msg['Subject'] = 'Your HTML Newsletter'
msg['From'] = 'newsletter@example.com'
msg['To'] = 'subscriber@example.com'
html_content = """
<html>
<head></head>
<body>
<h1>Welcome to Our Global Update!</h1>
<p>Dear Subscriber,</p>
<p>This is your <strong>latest update</strong> from around the world.</p>
<p>Visit our <a href="http://www.example.com">website</a> for more.</p>
<p>Best regards,<br>The Team</p>
</body>
</html>
"""
# Add the HTML version
msg.add_alternative(html_content, subtype='html')
# Add a plain text fallback
plain_text_content = (
"Welcome to Our Global Update!\n\n"
"Dear Subscriber,\n\n"
"This is your latest update from around the world.\n"
"Visit our website for more: http://www.example.com\n\n"
"Best regards,\nThe Team"
)
msg.add_alternative(plain_text_content, subtype='plain')
print(msg.as_string())
الشرح:
add_alternative()
يُستخدم لإضافة تمثيلات مختلفة لـ *نفس* المحتوى. سيقوم عميل البريد الإلكتروني بعرض "أفضل" واحد يمكنه التعامل معه (عادةً HTML).- هذا ينشئ تلقائيًا بنية MIME من نوع
multipart/alternative
.
التعامل مع المرفقات
إرفاق الملفات أمر بسيط باستخدام add_attachment()
. يمكنك إرفاق أي نوع من الملفات، وتتعامل الحزمة مع أنواع MIME والتشفيرات المناسبة (عادةً base64
).
from email.message import EmailMessage
from pathlib import Path
# Create dummy files for demonstration
Path('report.pdf').write_bytes(b'%PDF-1.4\n1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n2 0 obj<</Count 0>>endobj\nxref\n0 3\n0000000000 65535 f\n0000000009 00000 n\n0000000052 00000 n\ntrailer<</Size 3/Root 1 0 R>>startxref\n104\n%%EOF') # A very basic, invalid PDF placeholder
Path('logo.png').write_bytes(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x0cIDAT\x08\x99c`\x00\x00\x00\x02\x00\x01\xe2!\x00\xa0\x00\x00\x00\x00IEND\xaeB`\x82') # A 1x1 transparent PNG placeholder
msg = EmailMessage()
msg['Subject'] = 'Important Document and Image'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
msg.set_content('Please find the attached report and company logo.')
# Attach a PDF file
with open('report.pdf', 'rb') as f:
file_data = f.read()
msg.add_attachment(
file_data,
maintype='application',
subtype='pdf',
filename='Annual_Report_2024.pdf'
)
# Attach an image file
with open('logo.png', 'rb') as f:
image_data = f.read()
msg.add_attachment(
image_data,
maintype='image',
subtype='png',
filename='CompanyLogo.png'
)
print(msg.as_string())
# Clean up dummy files
Path('report.pdf').unlink()
Path('logo.png').unlink()
الشرح:
add_attachment()
يأخذ البايتات الخام لمحتوى الملف.maintype
وsubtype
يحددان نوع MIME (على سبيل المثال،application/pdf
،image/png
). هذه الأمور حاسمة لعميل البريد الإلكتروني للمستلم لتحديد المرفق والتعامل معه بشكل صحيح.filename
يوفر الاسم الذي سيتم حفظ المرفق تحته من قبل المستلم.- هذا يقوم تلقائيًا بإعداد بنية
multipart/mixed
.
إنشاء رسائل متعددة الأجزاء
عندما يكون لديك رسالة تحتوي على نص HTML، وبديل نصي عادي، وصور مضمنة أو ملفات ذات صلة، فأنت بحاجة إلى بنية متعددة الأجزاء أكثر تعقيدًا. يتعامل الصنف EmailMessage
مع هذا بذكاء باستخدام add_related()
و add_alternative()
.
السيناريو الشائع هو بريد إلكتروني HTML مع صورة مضمنة مباشرة داخل HTML (صورة "مضمنة"). هذا يستخدم multipart/related
.
from email.message import EmailMessage
from pathlib import Path
# Create a dummy image file for demonstration (a 1x1 transparent PNG)
Path('banner.png').write_bytes(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x0cIDAT\x08\x99c`\x00\x00\x00\x02\x00\x01\xe2!\x00\xa0\x00\x00\x00\x00IEND\xaeB`\x82')
msg = EmailMessage()
msg['Subject'] = 'Inline Image Example'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
# Plain text version (fallback)
plain_text = 'Check out our amazing banner!\n\n[Image: Banner.png]\n\nVisit our site.'
msg.set_content(plain_text, subtype='plain') # Set initial plain text content
# HTML version (with CID for inline image)
html_content = """
<html>
<head></head>
<body>
<h1>Our Latest Offer!</h1>
<p>Dear Customer,</p>
<p>Don't miss out on our special global promotion:</p>
<img src="cid:my-banner-image" alt="Promotion Banner">
<p>Click <a href="http://www.example.com">here</a> to learn more.</p>
</body>
</html>
"""
msg.add_alternative(html_content, subtype='html') # Add HTML alternative
# Add the inline image (related content)
with open('banner.png', 'rb') as img_file:
image_data = img_file.read()
msg.add_related(
image_data,
maintype='image',
subtype='png',
cid='my-banner-image' # This CID matches the 'src' in HTML
)
print(msg.as_string())
# Clean up dummy file
Path('banner.png').unlink()
الشرح:
set_content()
يحدد المحتوى الأولي (هنا، نص عادي).add_alternative()
يضيف نسخة HTML، مما ينشئ بنيةmultipart/alternative
التي تحتوي على أجزاء النص العادي و HTML.add_related()
يُستخدم للمحتوى الذي يكون "ذا صلة" بأحد أجزاء الرسالة، وعادة ما يكون صورًا مضمنة في HTML. يأخذ معلمةcid
(Content-ID)، والتي يتم الإشارة إليها بعد ذلك في وسم HTML<img src="cid:my-banner-image">
.- ستكون البنية النهائية
multipart/mixed
(إذا كانت هناك مرفقات خارجية) تحتوي على جزءmultipart/alternative
، والذي بدوره يحتوي على جزءmultipart/related
. يحتوي الجزءmultipart/related
على HTML والصورة المضمنة. يتعامل الصنفEmailMessage
مع هذا التعقيد المتداخل نيابة عنك.
الترميز ومجموعات الأحرف للوصول العالمي
للتواصل الدولي، يعد ترميز الأحرف الصحيح أمرًا بالغ الأهمية. حزمة email
، بشكل افتراضي، تفضل بشدة استخدام UTF-8، وهو المعيار العالمي للتعامل مع مجموعات الأحرف المتنوعة من جميع أنحاء العالم.
from email.message import EmailMessage
msg = EmailMessage()
msg['Subject'] = 'Global Characters: こんにちは, Привет, नमस्ते'
msg['From'] = 'global_sender@example.com'
msg['To'] = 'global_recipient@example.com'
# Japanese, Russian, and Hindi characters
content = "This message contains diverse global characters:\n"
content += "こんにちは (Japanese)\n"
content += "Привет (Russian)\n"
content += "नमस्ते (Hindi)\n\n"
content += "The 'email' package handles UTF-8 gracefully."
msg.set_content(content)
print(msg.as_string())
الشرح:
- عندما تتلقى
set_content()
سلسلة نصية من بايثون، فإنها تقوم تلقائيًا بترميزها إلى UTF-8 وتعيين ترويسةContent-Type: text/plain; charset="utf-8"
. - إذا تطلب المحتوى ذلك (على سبيل المثال، يحتوي على العديد من الأحرف غير ASCII)، فقد يطبق أيضًا
Content-Transfer-Encoding: quoted-printable
أوbase64
لضمان الإرسال الآمن عبر أنظمة البريد الإلكتروني القديمة. تتعامل الحزمة مع هذا تلقائيًا وفقًا للسياسة المختارة.
الترويسات والسياسات المخصصة
يمكنك إضافة أي ترويسة مخصصة إلى بريد إلكتروني. تحدد السياسات (من email.policy
) كيفية التعامل مع الرسائل، مما يؤثر على جوانب مثل ترميز الترويسات، ونهايات الأسطر، والتعامل مع الأخطاء. السياسة الافتراضية جيدة بشكل عام، ولكن يمكنك اختيار `SMTP` للامتثال الصارم لـ SMTP أو تحديد سياسات مخصصة.
from email.message import EmailMessage
from email import policy
msg = EmailMessage(policy=policy.SMTP)
msg['Subject'] = 'Email with Custom Header'
msg['From'] = 'info@example.org'
msg['To'] = 'user@example.org'
msg['X-Custom-Header'] = 'This is a custom value for tracking'
msg['Reply-To'] = 'support@example.org'
msg.set_content('This email demonstrates custom headers and policies.')
print(msg.as_string())
الشرح:
- استخدام
policy=policy.SMTP
يضمن الامتثال الصارم لمعايير SMTP، وهو أمر يمكن أن يكون حاسمًا لضمان وصول البريد. - تتم إضافة الترويسات المخصصة تمامًا مثل الترويسات القياسية. غالبًا ما تبدأ بـ
X-
للدلالة على الترويسات غير القياسية.
تحليل رسائل MIME: استخراج المعلومات من رسائل البريد الإلكتروني الواردة
يتضمن التحليل أخذ بيانات البريد الإلكتروني الأولية (التي يتم تلقيها عادةً عبر IMAP أو من ملف) وتحويلها إلى كائن `EmailMessage` يمكنك بعد ذلك فحصه والتعامل معه بسهولة.
التحميل والتحليل الأولي
ستتلقى رسائل البريد الإلكتروني عادةً كبايتات خام. يتم استخدام email.parser.BytesParser
(أو الوظائف المساعدة email.message_from_bytes()
) لهذا الغرض.
from email.parser import BytesParser
from email.policy import default
raw_email_bytes = b"""
From: sender@example.com
To: recipient@example.com
Subject: Test Email with Basic Headers
Date: Mon, 1 Jan 2024 10:00:00 +0000
Content-Type: text/plain; charset="utf-8"
This is the body of the email.
It's a simple test.
"""
# Using BytesParser
parser = BytesParser(policy=default)
msg = parser.parsebytes(raw_email_bytes)
# Or using the convenience function
# from email import message_from_bytes
# msg = message_from_bytes(raw_email_bytes, policy=default)
print(f"Subject: {msg['subject']}")
print(f"From: {msg['from']}")
print(f"Content-Type: {msg['Content-Type']}")
الشرح:
BytesParser
يأخذ بيانات البايتات الخام (وهي الطريقة التي يتم بها نقل رسائل البريد الإلكتروني) ويعيد كائنEmailMessage
.policy=default
يحدد قواعد التحليل.
الوصول إلى الترويسات
يمكن الوصول إلى الترويسات بسهولة عبر مفاتيح شبيهة بالقاموس. تتعامل الحزمة تلقائيًا مع فك تشفير الترويسات المشفرة (على سبيل المثال، المواضيع التي تحتوي على أحرف دولية).
# ... (using the 'msg' object from the previous parsing example)
print(f"Date: {msg['date']}")
print(f"Message ID: {msg['Message-ID'] if 'Message-ID' in msg else 'N/A'}")
# Handling multiple headers (e.g., 'Received' headers)
# from email.message import EmailMessage # If not imported yet
# from email import message_from_string # For a quick string example
multi_header_email = message_from_string(
"""
From: a@example.com
To: b@example.com
Subject: Multi-header Test
Received: from client.example.com (client.example.com [192.168.1.100])
by server.example.com (Postfix) with ESMTP id 123456789
for <b@example.com>; Mon, 1 Jan 2024 10:00:00 +0000 (GMT)
Received: from mx.another.com (mx.another.com [192.168.1.101])
by server.example.com (Postfix) with ESMTP id 987654321
for <b@example.com>; Mon, 1 Jan 2024 09:59:00 +0000 (GMT)
Body content here.
"""
)
received_headers = multi_header_email.get_all('received')
if received_headers:
print("\nReceived Headers:")
for header in received_headers:
print(f"- {header}")
الشرح:
- الوصول إلى ترويسة يعيد قيمتها كسلسلة نصية.
get_all('header-name')
مفيد للترويسات التي يمكن أن تظهر عدة مرات (مثلReceived
).- تتعامل الحزمة مع فك تشفير الترويسات، لذا فإن قيمًا مثل
Subject: =?utf-8?Q?Global_Characters:_=E3=81=93=E3=82=93=E3=81=AB=E3=81=A1=E3=81=AF?=
يتم تحويلها تلقائيًا إلى سلاسل نصية قابلة للقراءة.
استخراج محتوى النص الأساسي
يتطلب استخراج نص الرسالة الفعلي التحقق مما إذا كانت الرسالة متعددة الأجزاء. بالنسبة للرسائل متعددة الأجزاء، يمكنك التنقل عبر أجزائها.
from email.message import EmailMessage
from email import message_from_string
multipart_email_raw = """
From: multi@example.com
To: user@example.com
Subject: Test Multipart Email
Content-Type: multipart/alternative; boundary="_----------=_12345"
--_----------=_12345
Content-Type: text/plain; charset="utf-8"
Hello from the plain text part!
--_----------=_12345
Content-Type: text/html; charset="utf-8"
<html>
<body>
<h1>Hello from the HTML part!</h1>
<p>This is a <strong>rich text</strong> email.</p>
</body>
</html>
--_----------=_12345--
"""
msg = message_from_string(multipart_email_raw)
if msg.is_multipart():
print("\n--- Multipart Email Body ---")
for part in msg.iter_parts():
content_type = part.get_content_type()
charset = part.get_content_charset() or 'utf-8' # Default to utf-8 if not specified
payload = part.get_payload(decode=True) # Decode payload bytes
try:
decoded_content = payload.decode(charset)
print(f"Content-Type: {content_type}, Charset: {charset}\nContent:\n{decoded_content}\n")
except UnicodeDecodeError:
print(f"Content-Type: {content_type}, Charset: {charset}\nContent: (Binary or undecodable data)\n")
# Handle binary data, or attempt a fallback encoding
else:
print("\n--- Single Part Email Body ---")
charset = msg.get_content_charset() or 'utf-8'
payload = msg.get_payload(decode=True)
try:
decoded_content = payload.decode(charset)
print(f"Content-Type: {msg.get_content_type()}, Charset: {charset}\nContent:\n{decoded_content}\n")
except UnicodeDecodeError:
print(f"Content: (Binary or undecodable data)\n")
الشرح:
is_multipart()
تحدد ما إذا كان البريد الإلكتروني يحتوي على أجزاء متعددة.iter_parts()
يتكرر عبر جميع الأجزاء الفرعية لرسالة متعددة الأجزاء.get_content_type()
يعيد نوع MIME الكامل (على سبيل المثال،text/plain
).get_content_charset()
يستخرج مجموعة الأحرف من ترويسةContent-Type
.get_payload(decode=True)
أمر حاسم: فهو يعيد المحتوى *بعد فك تشفيره* كبايتات. ثم تحتاج إلى.decode()
هذه البايتات باستخدام مجموعة الأحرف الصحيحة للحصول على سلسلة نصية في بايثون.
التعامل مع المرفقات أثناء التحليل
المرفقات هي أيضًا أجزاء من رسالة متعددة الأجزاء. يمكنك التعرف عليها باستخدام ترويسة Content-Disposition
وحفظ محتواها بعد فك التشفير.
from email.message import EmailMessage
from email import message_from_string
import os
# Example email with a simple attachment
email_with_attachment = """
From: attach@example.com
To: user@example.com
Subject: Document Attached
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="_----------=_XYZ"
--_----------=_XYZ
Content-Type: text/plain; charset="utf-8"
Here is your requested document.
--_----------=_XYZ
Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="document.pdf"
JVBERi0xLjQKMSAwIG9iagpbL1BERi9UZXh0L0ltYWdlQy9JbWFnZUkvSW1hZ0VCXQplbmRvYmoK
--_----------=_XYZ--
"""
msg = message_from_string(email_with_attachment)
output_dir = 'parsed_attachments'
os.makedirs(output_dir, exist_ok=True)
print("\n--- Processing Attachments ---")
for part in msg.iter_attachments():
filename = part.get_filename()
if filename:
filepath = os.path.join(output_dir, filename)
try:
with open(filepath, 'wb') as f:
f.write(part.get_payload(decode=True))
print(f"Saved attachment: {filepath} (Type: {part.get_content_type()})")
except Exception as e:
print(f"Error saving {filename}: {e}")
else:
print(f"Found an attachment without a filename (Content-Type: {part.get_content_type()})")
# Clean up the output directory
# import shutil
# shutil.rmtree(output_dir)
الشرح:
iter_attachments()
يعيد بشكل خاص الأجزاء التي من المحتمل أن تكون مرفقات (أي، تحتوي على ترويسةContent-Disposition: attachment
أو غير مصنفة بطريقة أخرى).get_filename()
يستخرج اسم الملف من ترويسةContent-Disposition
.part.get_payload(decode=True)
يسترد المحتوى الثنائي الخام للمرفق، بعد فك تشفيره بالفعل منbase64
أوquoted-printable
.
فك تشفير الترميزات ومجموعات الأحرف
تقوم حزمة email
بعمل ممتاز في فك تشفير ترميزات النقل الشائعة (مثل base64
، quoted-printable
) تلقائيًا عند استدعاء get_payload(decode=True)
. بالنسبة للمحتوى النصي نفسه، تحاول استخدام charset
المحدد في ترويسة Content-Type
. إذا لم يتم تحديد مجموعة أحرف أو كانت غير صالحة، فقد تحتاج إلى التعامل معها برشاقة.
from email.message import EmailMessage
from email import message_from_string
# Example with a potentially problematic charset
email_latin1 = """
From: legacy@example.com
To: new_system@example.com
Subject: Special characters: àéíóú
Content-Type: text/plain; charset="iso-8859-1"
This message contains Latin-1 characters: àéíóú
"""
msg = message_from_string(email_latin1)
if msg.is_multipart():
for part in msg.iter_parts():
payload = part.get_payload(decode=True)
charset = part.get_content_charset() or 'utf-8'
try:
print(f"Decoded (Charset: {charset}): {payload.decode(charset)}")
except UnicodeDecodeError:
print(f"Failed to decode with {charset}. Trying fallback...")
# Fallback to a common charset or 'latin-1' if expecting it
print(f"Decoded (Fallback Latin-1): {payload.decode('latin-1', errors='replace')}")
else:
payload = msg.get_payload(decode=True)
charset = msg.get_content_charset() or 'utf-8'
try:
print(f"Decoded (Charset: {charset}): {payload.decode(charset)}")
except UnicodeDecodeError:
print(f"Failed to decode with {charset}. Trying fallback...")
print(f"Decoded (Fallback Latin-1): {payload.decode('latin-1', errors='replace')}")
الشرح:
- حاول دائمًا استخدام مجموعة الأحرف المحددة في ترويسة
Content-Type
. - استخدم كتلة
try-except UnicodeDecodeError
للمرونة، خاصة عند التعامل مع رسائل البريد الإلكتروني من مصادر متنوعة وربما غير قياسية. - يمكن استخدام
errors='replace'
أوerrors='ignore'
مع.decode()
للتعامل مع الأحرف التي لا يمكن تعيينها إلى الترميز المستهدف، مما يمنع حدوث أعطال.
سيناريوهات التحليل المتقدمة
يمكن أن تكون رسائل البريد الإلكتروني في العالم الحقيقي معقدة للغاية، مع هياكل متعددة الأجزاء متداخلة. الطبيعة التكرارية لحزمة email
تجعل التنقل في هذه الهياكل أمرًا مباشرًا. يمكنك دمج is_multipart()
مع iter_parts()
لاجتياز الرسائل المتداخلة بعمق.
from email.message import EmailMessage
from email import message_from_string
def parse_email_part(part, indent=0):
prefix = " " * indent
content_type = part.get_content_type()
charset = part.get_content_charset() or 'N/A'
print(f"{prefix}Part Type: {content_type}, Charset: {charset}")
if part.is_multipart():
for subpart in part.iter_parts():
parse_email_part(subpart, indent + 1)
elif part.get_filename(): # It's an attachment
print(f"{prefix} Attachment: {part.get_filename()} (Size: {len(part.get_payload(decode=True))} bytes)")
else: # It's a regular text/html body part
payload = part.get_payload(decode=True)
try:
decoded_content = payload.decode(charset)
# print(f"{prefix} Content (first 100 chars): {decoded_content[:100]}...") # For brevity
except UnicodeDecodeError:
print(f"{prefix} Content: (Binary or undecodable text)")
complex_email_raw = """
From: complex@example.com
To: receiver@example.com
Subject: Complex Email with HTML, Plain, and Attachment
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="outer_boundary"
--outer_boundary
Content-Type: multipart/alternative; boundary="inner_boundary"
--inner_boundary
Content-Type: text/plain; charset="utf-8"
Plain text content.
--inner_boundary
Content-Type: text/html; charset="utf-8"
<html><body><h2>HTML Content</h2></body></html>
--inner_boundary--
--outer_boundary
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="data.bin"
SGVsbG8gV29ybGQh
--outer_boundary--
"""
msg = message_from_string(complex_email_raw)
print("\n--- Traversing Complex Email Structure ---")
parse_email_part(msg)
الشرح:
- توضح الدالة التكرارية
parse_email_part
كيفية التنقل عبر شجرة الرسالة بأكملها، وتحديد الأجزاء متعددة الأجزاء، والمرفقات، ومحتوى النص في كل مستوى. - هذا النمط مرن للغاية لاستخراج أنواع معينة من المحتوى من رسائل البريد الإلكتروني المتداخلة بعمق.
البناء مقابل التحليل: منظور مقارن
بينما هما عمليتان متميزتان، فإن البناء والتحليل هما وجهان لعملة واحدة: التعامل مع رسائل MIME. فهم أحدهما يساعد حتمًا في فهم الآخر.
البناء (الإرسال):
- التركيز: تجميع الترويسات والمحتوى والمرفقات بشكل صحيح في بنية MIME متوافقة مع المعايير.
- الأداة الأساسية:
email.message.EmailMessage
مع طرق مثلset_content()
،add_attachment()
،add_alternative()
،add_related()
. - التحديات الرئيسية: ضمان أنواع MIME ومجموعات الأحرف الصحيحة (خاصة UTF-8 للدعم العالمي)، و
Content-Transfer-Encoding
، وتنسيق الترويسات بشكل صحيح. يمكن أن تؤدي الأخطاء إلى عدم عرض رسائل البريد الإلكتروني بشكل صحيح، أو تلف المرفقات، أو تصنيف الرسائل كبريد عشوائي (سبام).
التحليل (الاستقبال):
- التركيز: تفكيك تدفق بايتات البريد الإلكتروني الخام إلى أجزائه المكونة، واستخراج ترويسات محددة، ومحتوى النص، والمرفقات.
- الأداة الأساسية:
email.parser.BytesParser
أوemail.message_from_bytes()
، ثم التنقل في كائنEmailMessage
الناتج باستخدام طرق مثلis_multipart()
،iter_parts()
،get_payload()
،get_filename()
، والوصول إلى الترويسات. - التحديات الرئيسية: التعامل مع رسائل البريد الإلكتروني المشوهة، وتحديد ترميزات الأحرف بشكل صحيح (خاصة عندما تكون غامضة)، والتعامل مع الترويسات المفقودة، واستخراج البيانات بقوة من هياكل MIME المتنوعة.
يجب أن تكون الرسالة التي تبنيها باستخدام `EmailMessage` قابلة للتحليل تمامًا بواسطة `BytesParser`. وبالمثل، فإن فهم بنية MIME الناتجة أثناء التحليل يمنحك نظرة ثاقبة حول كيفية بناء رسائل معقدة بنفسك.
أفضل الممارسات للتعامل مع البريد الإلكتروني العالمي باستخدام بايثون
للتطبيقات التي تتفاعل مع جمهور عالمي أو تتعامل مع مصادر بريد إلكتروني متنوعة، ضع في اعتبارك هذه الممارسات الأفضل:
- التوحيد على UTF-8: استخدم دائمًا UTF-8 لجميع المحتوى النصي، سواء عند البناء أو عند توقعه أثناء التحليل. هذا هو المعيار العالمي لترميز الأحرف ويتجنب النص المشوه (mojibake).
- التحقق من صحة عناوين البريد الإلكتروني: قبل الإرسال، تحقق من صحة عناوين البريد الإلكتروني للمستلمين لضمان إمكانية التسليم. أثناء التحليل، كن مستعدًا لعناوين قد تكون غير صالحة أو مشوهة في ترويسات `From`، `To`، أو `Cc`.
- الاختبار الصارم: اختبر بناء بريدك الإلكتروني مع مختلف عملاء البريد الإلكتروني (Gmail, Outlook, Apple Mail, Thunderbird) والمنصات لضمان عرض متسق لـ HTML والمرفقات. بالنسبة للتحليل، اختبر بمجموعة واسعة من رسائل البريد الإلكتروني النموذجية، بما في ذلك تلك ذات الترميزات غير العادية، أو الترويسات المفقودة، أو الهياكل المتداخلة المعقدة.
- تعقيم المدخلات المحللة: تعامل دائمًا مع المحتوى المستخرج من رسائل البريد الإلكتروني الواردة على أنه غير موثوق به. قم بتعقيم محتوى HTML لمنع هجمات XSS إذا كنت تعرضه في تطبيق ويب. تحقق من صحة أسماء المرفقات وأنواعها لمنع ثغرات أمنية مثل اجتياز المسار (path traversal) عند حفظ الملفات.
- معالجة قوية للأخطاء: قم بتنفيذ كتل
try-except
شاملة عند فك تشفير الحمولات أو الوصول إلى ترويسات قد تكون مفقودة. تعامل برشاقة معUnicodeDecodeError
أوKeyError
. - التعامل مع المرفقات الكبيرة: كن واعيًا لأحجام المرفقات، سواء عند البناء (لتجنب تجاوز حدود خادم البريد) أو التحليل (لمنع الاستخدام المفرط للذاكرة أو استهلاك مساحة القرص). فكر في بث المرفقات الكبيرة إذا كان نظامك يدعم ذلك.
- استخدام
email.policy
: بالنسبة للتطبيقات الحرجة، اختر صراحةً `email.policy` (على سبيل المثال، `policy.SMTP`) لضمان الامتثال الصارم لمعايير البريد الإلكتروني، مما يمكن أن يؤثر على إمكانية التسليم والتشغيل البيني. - الحفاظ على البيانات الوصفية: عند التحليل، قرر ما هي البيانات الوصفية (الترويسات، سلاسل الحدود الأصلية) المهمة للحفاظ عليها، خاصة إذا كنت تبني نظام أرشفة أو إعادة توجيه للبريد.
الخاتمة
تعد حزمة email
في بايثون مكتبة قوية ومرنة بشكل لا يصدق لأي شخص يحتاج إلى التفاعل برمجيًا مع البريد الإلكتروني. من خلال إتقان كل من بناء رسائل MIME والتحليل القوي لرسائل البريد الإلكتروني الواردة، فإنك تطلق العنان للقدرة على إنشاء أنظمة أتمتة بريد إلكتروني متطورة، وبناء عملاء بريد إلكتروني، وتحليل بيانات البريد الإلكتروني، ودمج وظائف البريد الإلكتروني في أي تطبيق تقريبًا.
تتعامل الحزمة بعناية مع التعقيدات الأساسية لـ MIME، مما يسمح للمطورين بالتركيز على محتوى ومنطق تفاعلاتهم مع البريد الإلكتروني. سواء كنت ترسل نشرات إخبارية مخصصة لجمهور عالمي أو تستخرج بيانات حيوية من تقارير الأنظمة الآلية، فإن الفهم العميق لحزمة email
سيثبت أنه لا يقدر بثمن في بناء حلول بريد إلكتروني موثوقة وقابلة للتشغيل البيني ومدركة عالميًا.